-
Notifications
You must be signed in to change notification settings - Fork 0
Tempo control refactor with arpeggiator, synth, and UI/docs #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make BPM stateful in useSequencer hook with real-time tempo changes - Add editable number input for BPM (60-200 range) - Persist BPM in URL state (backwards compatible with old URLs) - Set up Vitest testing infrastructure - Add tests for TransportControls, Pad, instruments config, and URL state - Include tempo change tests for input validation and blur/Enter behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 16-step arpeggiator with up/down/up-down modes and rate control - Add polyphonic synth engine with waveform selection (saw/square/sine/triangle) - Add 2-octave keyboard with toggle mode for held notes - Add audio visualizers that respond to playing sounds - Restructure UI layout with drums section and synth section - Add track labels (HI-HAT, SNARE, KICK) - Add per-track volume/reverb/filter knobs - Improve container alignment with fit-content sizing - Set default dev server port to 3000 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
brsbl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review Summary
I've reviewed the PR and found one blocking issue that will cause CI failures. The overall implementation of the arpeggiator, synth engine, and UI improvements is well-structured.
Overall Correctness Verdict: INCORRECT
The patch contains a bug in the CI configuration that will cause the workflow to fail on every run. The workflow references npm scripts that do not exist in package.json.
brsbl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review Summary
I've reviewed the PR and found one blocking issue that will cause CI failures. The overall implementation of the arpeggiator, synth engine, and UI improvements is well-structured.
Overall Correctness Verdict: INCORRECT
The patch contains a bug in the CI configuration that will cause the workflow to fail on every run. The workflow references npm scripts that do not exist in package.json.
.github/workflows/ci.yml
Outdated
| run: npm run lint | ||
|
|
||
| - name: Run tests | ||
| run: npm run test:coverage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P0] CI workflow references non-existent npm scripts
The CI workflow calls npm run format:check, npm run lint, and npm run test:coverage, but none of these scripts exist in package.json. The package.json only defines test and test:watch scripts. This will cause every CI run to fail immediately with an error like npm ERR! Missing script: "format:check".
Either add the missing scripts to package.json, or update the CI workflow to use existing scripts (e.g., npm run test instead of npm run test:coverage).
Resolve merge conflicts: - Keep vitest for testing (over jest) - Merge UI dependencies from main - Add tw-animate-css and theme variables - Add path alias for @ imports - Combine BPM persistence with filter settings in urlState 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| gainNode.connect(masterGainRef.current) | ||
|
|
||
| // Start oscillator | ||
| oscillator.start(now) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P0] Unsynchronized playback
The synth engine triggers notes immediately using ctx.currentTime. This ignores the precise scheduling time (nextNoteTimeRef) calculated by the sequencer lookahead. This will cause the synth to play out of sync with the drums (approximately scheduleAheadTime early) and suffer from main-thread timing jitter.
playNote should accept a time parameter and use it: oscillator.start(time).
src/hooks/useArpeggiator.js
Outdated
| } | ||
|
|
||
| // Play new note | ||
| synth.playNote('arp-' + noteName, noteData.freq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P0] Missing timing parameter
The noteTime parameter (calculated for precise scheduling) is received here but not passed to synth.playNote. This prevents the synth from playing in sync with the sequencer grid.
| synth.playNote('arp-' + noteName, noteData.freq) | |
| synth.playNote('arp-' + noteName, noteData.freq, noteTime) |
src/hooks/useAudioEngine.js
Outdated
| const nodes = effectNodesRef.current[instrumentId] | ||
| if (!nodes || !audioContextRef.current) return | ||
|
|
||
| const sliderValue = parseInt(value, 10) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P0] Broken effects control
parseInt will truncate the 0-1 float values from the UI sliders to either 0 or 1, effectively breaking the reverb and delay controls (which will mostly stay at 0). Additionally, the subsequent division by 10 implies the code expects 0-10 or 0-100, but the sliders provide 0-1.
| const sliderValue = parseInt(value, 10) | |
| const sliderValue = parseFloat(value) |
| nodes.lowpassFilter.frequency.setValueAtTime(cutoff, currentTime) | ||
| break | ||
| } | ||
| if (effectType === 'reverb') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Missing Filter implementation
The lowpassFilter node and its control logic have been removed from the audio graph, but the UI still exposes a Filter knob that calls this function with effectType === 'filter'. Currently, the filter knob does nothing.
| onReset={sequencer.resetPattern} | ||
| arpeggiator={arpeggiator} | ||
| synth={synth} | ||
| analyser={audioEngine.analyser} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Visualizer missing synth
The visualizer is connected only to the audioEngine (drums) analyser. The synth engine uses a separate AudioContext and signal chain, so synth notes will not appear in the visualizer.
- Convert useAudioEngine.test.js from Jest to Vitest imports - Update urlState.test.js to match new encoding format (volume/reverb/filter) - Fix Button component default export (remove shadcn conflict) - Add ResizeObserver global declaration in AudioVisualizer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The ci.yml workflow already handles testing with pnpm. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@terragon-labs please use subagents to review all PR feedback, create a plan to address each one, then have additional subagents review each fix |
…alizer - Add optional time parameter to useSynthEngine.playNote() for precise Web Audio scheduling instead of immediate ctx.currentTime - Pass noteTime from arpeggiator to synth.playNote() for grid sync - Fix effects control: change parseInt to parseFloat for 0-1 slider values and remove incorrect /10 division in reverb/delay calculations - Restore lowpassFilter implementation with exponential frequency mapping (200Hz-20kHz) and proper signal chain routing - Share AudioContext between drum and synth engines so visualizer displays both audio sources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
PR Review Fixes Applied ✅I've reviewed all the PR feedback using subagents and implemented fixes for each issue: Issues Fixed
Changes Made
Verification
|
…encies Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
brsbl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review Summary
I've reviewed this PR which introduces a tempo-controlled arpeggiator, polyphonic synth engine, expanded UI structure, and audio visualizers.
Overall Assessment
The code is well-structured with good separation of concerns:
- React hooks are properly designed with refs for non-reactive state and useCallback for memoization
- Audio engine properly handles Web Audio API lifecycle (context creation, cleanup on unmount)
- The synth engine correctly handles shared vs standalone AudioContext modes
- URL state encoding/decoding is robust with backward compatibility
- Tests provide reasonable coverage for the core functionality
Findings
No blocking bugs identified. The implementation appears solid with proper cleanup, error handling, and consideration for edge cases like:
- Multiple audio context initialization prevention via
initCountRef - Proper event listener cleanup in hooks
- Suspended AudioContext handling for browser autoplay policies
- Backward compatibility for URL state format changes
The code follows React best practices and the audio scheduling is implemented correctly using the standard web audio scheduler pattern.
Verdict: ✅ Correct
The patch does not introduce bugs that would break existing functionality or tests.
brsbl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewing the changes. There are a few issues to address regarding the arpeggiator logic and potential performance risks.
|
[P1] Arpeggiator notes sustain indefinitely over empty steps In However, This means if the pattern has gaps (e.g., Suggestion: // Only play on matching steps
if (currentStep % skipSteps !== 0) {
return
}
// Check if gate is open for this step
if (!pattern[currentStep]) {
// Gate is closed. Stop previous note if it's still playing.
if (lastPlayedNoteRef.current) {
synth.stopNote('arp-' + lastPlayedNoteRef.current)
lastPlayedNoteRef.current = null
}
return
} |
|
[P2] Potential AudioContext re-initialization on render In useEffect(() => {
// ... initAudio ...
}, [instruments])If the Suggestion: |
Summary
What changed
Files touched (high level)
Test plan
🤖 Generated with Claude Code
📎 Task: https://www.terragonlabs.com/task/7110b41e-3846-4458-aa5f-2403afeab236